// 12.1 动态内存与智能指针
/**
 * 在C++中，动态内存的管理是通过一对运算符来完成的：
 * 1. new，在动态内存中为对象分配空间并返回一个指向该对象的指针，我们可以选择对对象进行初始化；
 * 2. delete，接受一个动态对象的指针，销毁该对象，并释放与之关联的内存。
 * 新标准库提供的这两种智能指针的区别在于管理底层指针的方式：
 * 1. shared_ptr允许多个指针指向同一个对象；
 * 2. unique_ptr则“独占”所指向的对象。
 * 标准库还定义了一个名为weak_ptr的伴随类，它是一种弱引用，指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include "12.1.1.cc" // 不能编译，因为重复定义的main函数

// 对于访问一个不存在的元素的尝试，StrBlobPtr抛出一个异常
class StrBlobPtr
{
public:
    // 解引用运算符
    std::string& operator*() const 
    {
         auto p = check(curr, "dereference past end");
         return (*p)[curr]; // (*p)是对象所指的vector
    }
    // 箭头运算符
    std::string* operator->() const 
    {
        // 将实际工作委托给解引用运算符
        return & this->operator*();
    }
    // 递增和递减运算符。为了与内置版本保持一致，前置运算符应该返回递增或递减后对象的引用。
    StrBlobPtr& operator++();   // 前置运算符
    StrBlobPtr& operator--();
    // 递增和递减运算符。后置版本
    StrBlobPtr operator++(int); // 后置运算符
    StrBlobPtr operator--(int);
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(StrBlob &a, size_t sz=0) : wptr(a.data), curr(sz) {}
    std::string& deref() const;
    StrBlobPtr& incr(); // 前缀递增

private:
    std::size_t curr; // 在数组中当前的位置
    std::weak_ptr<std::vector<std::string>> wptr; // 保存一个weak_ptr，意味着底层vector可能被销毁
    std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const; // 若检查成功，check返回一个指向vector的shared_ptr
};
// StrBlobPtr的check成员与StrBlob中的同名成员不同，它还要检查指针指向的vector是否还存在：
std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string& msg) const
{
    auto ret = wptr.lock(); // vector还存在吗？
    if(!ret)
        throw std::runtime_error("unbound StrBlobPtr");
    if(i >= ret->size())
        throw std::out_of_range(msg);
    return ret; // 否则，返回指向vector的shared_ptr
}
// 我们将在第14章学习如何定义自己的运算符。现在，我们将定义名为deref和incr的函数，分别用来解引用和递增StrBlobPtr。
std::string& StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end"); // deref成员调用check，检查使用vector是否安全以及curr是否在合法范围内：
    // 如果check成功，p就是一个shared_ptr，指向StrBlobPtr所指向的vector。表达式（＊p）[curr]解引用shared_ptr来获得vector，然后使用下标运算符提取并返回curr位置上的元素。
    return (*p)[curr]; // *p是对象所指向的vector。
}
// 前缀递增，返回递增后的引用对象
StrBlobPtr& StrBlobPtr::incr()
{
    // 如果curr已指向容器的尾后位置，就不能递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr; // 推进当前位置
    return *this;
}
// 前置版本，返回递增或递减对象的引用
StrBlobPtr& StrBlobPtr::operator++()
{
    // 如果curr已指向容器的尾后位置，则无法递增它
    check(curr, "increment past end of StrBlobPtr");
    ++curr; // 推进当前位置
    return *this;
}
StrBlobPtr& StrBlobPtr::operator--()
{
    // 如果curr是0，则继续递减它将产生一个无效的下标
    --curr; // 将curr在当前状态下向后移动一个元素
    // 递减运算符先递减curr，然后调用check函数。此时，如果curr（一个无符号数）已经是0了，那么我们传递给check的值将是一个表示无效下标的非常大的正数值（参见2.1.2节，第33页）。
    check(curr, "decrement past begin of StrBlobPtr"); 
    return *this;
}
// 后置版本，返回递增或递减对象的原值
StrBlobPtr StrBlobPtr::operator++(int) // 因为我们不会用到int形参，所以无须为其命名。
{
    // 此处无需检查有效性，调用前置递增运算符时才需要检查
    StrBlobPtr ret = *this; // 记录当前的值
    ++*this;                // 向前移动一个元素，前置++需要检查递增的有效性
    return ret;             // 返回之前记录的状态
}
StrBlobPtr StrBlobPtr::operator--(int) // 因为我们不会用到int形参，所以无须为其命名。
{
    // 此处无需检查有效性，调用前置递增运算符时才需要检查
    StrBlobPtr ret = *this; // 记录当前的值
    --*this;                // 向后移动一个元素，前置--需要检查递增的有效性
    return ret;             // 返回之前记录的状态
}
// 由上可知，我们的后置运算符调用各自的前置版本来完成实际的工作。例如后置递增运算符执行++*this

int main()
{
    // 12.1.6 weak_ptr
    // weak_ptr（见表12.5）是一种不控制所指向对象生存期的智能指针，它指向由一个shared_ptr管理的对象。
    // 将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。
    // 1. weak_ptr<T> w 空weak_ptr可以指向类型为T的对象
    // 2. weak_ptr<T> w(sp) 与shared_ptr sp指向相同对象的weak_ptr。T必须能转换为sp指向的类型
    // 3. w = p p可以是shared_ptr或weak_ptr。赋值后，w与p共享对象
    // 4. w.reset() 将w置为空
    // 5. w.user_count() 与w共享对象的shared_ptr的数量
    // 6. w.expired() 若w.use_count()为0，返回true，否则false
    // 7. w.lock() 如果w.expired()为true，返回一个空shared_ptr，否则返回一个指向w的对象的shared_ptr
    // 当我们创建一个weak_ptr时，要用一个shared_ptr来初始化它：
    auto p = make_shared<int>(42);
    weak_ptr<int> wp(p); // wp弱共享p，p的引用计数不变
    // 由于对象可能不存在，我们不能使用weak_ptr直接访问对象，而必须调用lock。此函数检查weak_ptr指向的对象是否仍存在。
    // 如果存在，lock返回一个指向共享对象的shared_ptr。
    if(shared_ptr<int> np = wp.lock()) // 如果np不为空则条件成立
    {
        // 在if中，np与p共享对象
    }
    // 只有当lock调用返回true时我们才会进入if语句体。在if中，使用np访问共享对象是安全的。

    // 核查指针类
    // 由于一个weak_ptr不参与其对应的shared_ptr的引用计数，StrBlobPtr指向的vector可能已经被释放了。如果vector已销毁，lock将返回一个空指针。
    // 在本例中，任何vector的引用都会失败，于是抛出一个异常。否则，check会检查给定索引，如果索引值合法，check返回从lock获得的shared_ptr。

    // 指针操作
    // 我们将在第14章学习如何定义自己的运算符。现在，我们将定义名为deref和incr的函数，分别用来解引用和递增StrBlobPtr。



    return 0;
}